package pt.isec.a21210392.a21190220.ludotwist;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;

import pt.isec.a21210392.a21190220.ludotwist.GameLogic.Android;
import pt.isec.a21210392.a21190220.ludotwist.GameLogic.Board;
import pt.isec.a21210392.a21190220.ludotwist.GameLogic.Game;
import pt.isec.a21210392.a21190220.ludotwist.GameLogic.Human;
import pt.isec.a21210392.a21190220.ludotwist.GameLogic.Pin;
import pt.isec.a21210392.a21190220.ludotwist.GameLogic.Player;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.SimpleAdapter;
import android.widget.TextView;

public class GameActivity extends Activity
{
	public static int[] playerColors = {Color.BLUE,Color.YELLOW,Color.GREEN,Color.RED};
	public static Board board;
	public static Game game;
	public static BoardView boardView;
	private Human me;
	private String myName = "Human";
	private ArrayList<Human> humanPlayers;
	private int diceResult;
	private int diceResultAndroid;
	private Button rollDice;
	private Button viewOptions;
	private ImageView diceResults;
	private TextView playerName;
	private ArrayList<HashMap<String,Integer>> options;
	private ArrayList<Integer> optionsNumber;
	private ArrayList<Thread> threads;
	private boolean go = false;
	public static boolean justStarted = true;
	private Player winner;
	
	private DisplayMetrics metrics;	
	private SimpleAdapter simpleAdapter;
	private AlertDialog.Builder choosePinDialog;
	private AlertDialog.Builder finish;
	
	private Message message;
	public static Socket socket = null;
	private ObjectInputStream in;
	private static ObjectOutputStream out;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) 
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_game);
		
		StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
		StrictMode.setThreadPolicy(policy);
		
		android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
		metrics = new DisplayMetrics();
		getWindowManager().getDefaultDisplay().getMetrics(metrics);
		
		boardView = new BoardView(this,metrics);
		FrameLayout fl = (FrameLayout)findViewById(R.id.boardZone);
		LayoutParams parameters = fl.getLayoutParams();
		parameters.width = metrics.widthPixels-10;
		parameters.height = metrics.widthPixels-10;
		fl.setLayoutParams(parameters);
		fl.addView(boardView);
		
		rollDice = (Button)findViewById(R.id.throwDice);
		viewOptions = (Button)findViewById(R.id.viewOptions);
		viewOptions.setEnabled(false);
		diceResults = (ImageView)findViewById(R.id.diceResult);
		playerName = (TextView)findViewById(R.id.playerName);
		
		humanPlayers = new ArrayList<Human>();
		options = new ArrayList<HashMap<String,Integer>>();
		optionsNumber = new ArrayList<Integer>();
		
		boolean hasJoinSocket = getIntent().getBooleanExtra("fromJoin", false);
		boolean hasHostSocket = getIntent().getBooleanExtra("fromHost", false);
		int playerNumber = getIntent().getIntExtra("number", 0);
		
		board = new Board();
		File myFile = new File("/sdcard/ludotwistprofile.txt");
		if (myFile.exists())
		{
			try
			{
				FileInputStream fis = new FileInputStream(myFile);
				InputStreamReader isr = new InputStreamReader(fis);
				BufferedReader br = new BufferedReader(isr);
				myName = br.readLine();
				br.close();
				isr.close();
				fis.close();
			}
			catch(Exception e){Log.d("A ler ficheiro...",e.getMessage().toString());}
		}
		me = new Human(myName,playerNumber);
		
		diceResults.setImageResource(R.drawable.transparent);
		rollDice.setEnabled(false);
		playerName.setText("");
		
		if(hasHostSocket)
		{
			hostGame();
		}
		else if(hasJoinSocket) //send name to host
		{			
			joinGame();
		}
		else
		{
			humanPlayers.add(me);
			game = new Game(humanPlayers);
			game.setBoard(board);
			threads = new ArrayList<Thread>();
			
			threads.add(new HumanThread(me));
			for(int i=0; i<4; i++)
			{
				Player player = game.getPlayers().get(i);
				if (player instanceof Android)
				{
					threads.add(new AndroidThread((Android)player));
				}
			}
			for (Thread t: threads)
			{
				t.start();
			}
			playerName.setText(me.getName());
		}
		warnThreads();
		justStarted = false;
	}
	@Override
	public boolean onCreateOptionsMenu(Menu menu) 
	{
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.game, menu);
		return true;
	}
	@Override
	protected void onDestroy() 
	{
		if (socket != null && !game.finished())
		{
			try
			{
				out.writeObject(new Message(Constants.FINISH_PLAY,winner));
				out.flush();
			}
			catch(Exception e){}
		}
		justStarted = true;
		super.onDestroy();
	}
	
	public void hostGame()
	{
		socket = HostGameActivity.socket;
		Log.d("gameactivity:", "ja temos socket!");
		
		humanPlayers.add(me);
		
		int humans = getIntent().getIntExtra("players",1);
		int aux = humans;
		
		try
		{
			out = new ObjectOutputStream(socket.getOutputStream());
			out.flush();
			Log.d("gameactivity:", "ja temos out!");
			in = HostGameActivity.in;
			Log.d("gameactivity:", "ja temos in!");
			
			while (aux != 1)
			{
				Log.d("gameactivity:", "estamos no ciclo dos nomes");
				message = (Message)in.readObject();
				Log.d("gameactivity:", "recebi " + message.getMessage());
				if(message.getMessage().equals(Constants.NAME))
				{
					humanPlayers.add((Human)message.getObject());
					aux--;
				}
			}
			Log.d("gameactivity:", "vamos construir o jogo!");
			game = new Game(humanPlayers);
			Log.d("gameactivity:", "ja temos jogo!");
			game.setBoard(board);
			
			out.writeObject(new Message(Constants.PLAY,game));
			out.flush();
			updateBoard();
		}
		catch(Exception e){Log.d("A receber nomes...",e.getMessage().toString());};
		
		threads = new ArrayList<Thread>();
		threads.add(new HumanThread(me));
		
		if (humans != 4)
		{				
			for(int i=0; i<4; i++)
			{
				Player player = game.getPlayers().get(i);
				if (player instanceof Android)
				{
					threads.add(new AndroidThread((Android)player));
				}
			}
		}
		for (Thread t: threads)
		{
			t.start();
		}
		new CommunicationThread().start();
	}
	public void joinGame()
	{
		socket = JoinGameActivity.socket;
		in = JoinGameActivity.in;
		Log.d("Em join: ","static inicializados");
		try
		{
			out = new ObjectOutputStream(socket.getOutputStream());
			Log.d("Em join: ","output aberto");
			out.writeObject(new Message(Constants.NAME, me));
			out.flush();
			Log.d("Em join: ","nome enviado");
			
			while(!go)
			{
				message = (Message)in.readObject();
				Log.d("Em join: ","recebido "+message.getMessage());
				if(message.getMessage().equals(Constants.PLAY))
				{
					game = (Game)message.getObject();
					board = game.getBoard();
				}
				if(message.getMessage().equals(Constants.UPDATE_BOARD))
				{
					final ArrayList<ToDraw> draws = (ArrayList<ToDraw>)message.getObject();
					runOnUiThread(new Runnable() 
					{							
						@Override
						public void run() 
						{
							boardView.clearDraws();
							boardView.setDraws(draws);
						}
					});
					go = true;
				}
			}
			go = false;
		}
		catch(Exception e){Log.d("A enviar nome...",e.getMessage().toString());};
		
		threads = new ArrayList<Thread>();
		threads.add(new HumanThread(me));
		for (Thread t: threads)
		{
			t.start();
		}
		new CommunicationThread().start();
	}
	public void onRollDice(View v)
	{
		rollDice.setEnabled(false);
		viewOptions.setEnabled(true);
		diceResult = (int)(Math.random()*6+1);
		int image = (diceResult == 1) ? R.drawable.d6_1 : (diceResult == 2) ? R.drawable.d6_2 : (diceResult == 3) ? R.drawable.d6_3 : (diceResult == 4) ? R.drawable.d6_4 : (diceResult == 5) ? R.drawable.d6_5 : R.drawable.d6_6;   
		diceResults.setImageResource(image);
		if (socket != null)
		{
			updateDice(Integer.valueOf(diceResult));
		}
		go = true;
	}
	public void onViewOptions(View v)
	{
		choosePinDialog.show();
	}
	public static void updateBoard()
	{
		ArrayList<ToDraw> draws = boardView.getDraws();
		try
		{
			out.reset();
			out.writeObject(new Message(Constants.UPDATE_BOARD,draws));
			out.flush();
		}
		catch(Exception e){Log.d("A fazer updateBoard", e.getMessage().toString());}
	}
	public void updateDice(int result)
	{
		try
		{
			out.reset();
			out.writeObject(new Message(Constants.UPDATE_DICE,result));
			out.flush();
		}
		catch(Exception e){Log.d("A fazer updateDice", e.getMessage().toString());}
	}

	public void finishTurn()
	{
		if (socket != null)
		{
			if (game.getCurrentPlayer().winner())
			{
				winner = game.getCurrentPlayer();
				try
				{
					out.writeObject(new Message(Constants.FINISH_PLAY,winner));
					out.flush();
				}
				catch(Exception e){Log.d("A terminar jogo", e.getMessage().toString());}
			}
			else
			{
				game.switchPlayer();
				updateBoard();
				try
				{
					out.reset();
					out.writeObject(new Message(Constants.FINISH_TURN, game));
					out.flush();
				}
				catch(Exception e){Log.d("A terminar turno", e.getMessage().toString());}
			}
		}
		else
		{
			if (game.getCurrentPlayer().winner())
			{
				winner = game.getCurrentPlayer();
				game.finish();
			}
			else
			{
				game.switchPlayer();
				warnThreads();
			}
		}
	}
	public void warnThreads()
	{
		for (Thread t: threads)
		{
			if (t instanceof HumanThread)
			{
				((HumanThread) t).refresh();
				continue;
			}
			((AndroidThread) t).refresh();
		}
	}
	
	public class CommunicationThread extends Thread
	{		
		@Override
		public void run()
		{
			while(!game.finished())
			{
				try
				{
					message = (Message)in.readObject();
					Log.d("Em CommThread", "recebi " + message.getMessage());
					
					if (message.getMessage().equals(Constants.UPDATE_DICE))
					{
						int result = (Integer)message.getObject();
						final int image = (result == 1) ? R.drawable.d6_1 : (result == 2) ? R.drawable.d6_2 : (result == 3) ? R.drawable.d6_3 : (result == 4) ? R.drawable.d6_4 : (result == 5) ? R.drawable.d6_5 : R.drawable.d6_6;
						runOnUiThread(new Runnable() 
						{							
							@Override
							public void run() 
							{								
								diceResults.setImageResource(image);
							}
						});
						continue;
					}
					if (message.getMessage().equals(Constants.UPDATE_BOARD))
					{
						final ArrayList<ToDraw> draws = (ArrayList<ToDraw>)message.getObject();
						runOnUiThread(new Runnable() 
						{							
							@Override
							public void run() 
							{
								boardView.clearDraws();
								boardView.setDraws(draws);
							}
						});
						continue;
					}
					if (message.getMessage().equals(Constants.FINISH_TURN))
					{
						game = (Game)message.getObject();
						board = game.getBoard();
						warnThreads();
						continue;
					}
					if (message.getMessage().equals(Constants.FINISH_PLAY))
					{						
						try
						{
							out.writeObject(new Message(Constants.SET_FINISH,null));
							out.flush();
						}
						catch(Exception e){Log.d("A terminar...",e.getMessage().toString());}
						winner = (Player)message.getObject();
						game.finish();
					}
				}
				catch(Exception e){Log.d("Em CommThread", e.getMessage().toString());}
			}
			try
			{
				justStarted = true;
				socket.close();
				socket = null;
			}
			catch(Exception e){Log.d("En CommTread",e.getMessage().toString());}
		}
	}
	public class AndroidThread extends Thread
	{
		private Android android;
		private boolean androidMustPlayAgain = false;
		private boolean goAndroid = false;
		
		public AndroidThread(Android android)
		{
			this.android = android;
		}		
		public void androidMustPlay(Pin pin)
		{
			if (pin.atTheBegin())
			{
				pin.putOut();
				if (diceResultAndroid == 6)
				{
					androidMustPlayAgain = true;
					return;
				}
			}
			else
			{
				pin.moveForward(diceResultAndroid);
			}
			if (pin.shouldRollAgain())
			{
				androidMustPlayAgain = true;
				return;
			}
			androidMustPlayAgain = false;
		}		
		public void refresh()
		{
			if (game.getCurrentPlayer().equals(android))
			{
				if (socket != null)
				{
					android = (Android)game.getCurrentPlayer();
				}
				goAndroid = true;
			}
		}
		
		@Override
		public void run()
		{
			while(!game.finished())
			{
				if (goAndroid)
				{
					goAndroid = false;
					
					do
					{
						try
						{
							this.sleep(Constants.turnChange);
						}
						catch(Exception e){}
						diceResultAndroid = (int)(Math.random()*6+1);
						final int image = (diceResultAndroid == 1) ? R.drawable.d6_1 : (diceResultAndroid == 2) ? R.drawable.d6_2 : (diceResultAndroid == 3) ? R.drawable.d6_3 : (diceResultAndroid == 4) ? R.drawable.d6_4 : (diceResultAndroid == 5) ? R.drawable.d6_5 : R.drawable.d6_6;
						runOnUiThread(new Runnable() 
						{
							@Override
							public void run() 
							{								   
								diceResults.setImageResource(image);
							}
						});
						if(socket != null)
						{
							updateDice(Integer.valueOf(diceResultAndroid));
						}
						try
						{
							this.sleep(Constants.turnChange);
						}
						catch(Exception e){}
						
						options.clear();
						options = game.getOptions(diceResultAndroid, optionsNumber);
						
						if (options.size() != 0)
						{
							int random = (int)(Math.random()*options.size());
							androidMustPlay(android.getPins().get(optionsNumber.get(random)));
						}
					}
					while(androidMustPlayAgain);
					finishTurn();
				}
			}
		}
	}
	public class HumanThread extends Thread
	{
		private Human human;
		private boolean humanSelectedPin = false;
		private Pin pin;
		
		public HumanThread(Human human) 
		{
			this.human = human;
		}
		
		public void setRollDiceEnabled()
		{
			runOnUiThread(new Runnable() 
			{					
				@Override
				public void run() 
				{
					rollDice.setEnabled(true);
				}
			});		
		}
		public void humanMustPlay(Pin pin)
		{
			if (pin.atTheBegin())
			{
				pin.putOut();
				if (diceResult == 6)
				{				
					setRollDiceEnabled();
					return;
				}
			}
			else
			{
				pin.moveForward(diceResult);
			}
			if (pin.shouldRollAgain())
			{
				setRollDiceEnabled();		
				return;
			}
			finishTurn();
		}		
		public void refresh()
		{
			runOnUiThread(new Runnable()
			{
				@Override
				public void run() 
				{
					diceResults.setImageResource(R.drawable.transparent);
					playerName.setText(game.getCurrentPlayer().getName());
					playerName.setTextColor(playerColors[game.getCurrentPlayer().getNumber()]);
				}
			});
			if (game.getCurrentPlayer().equals(human))
			{
				if (socket != null)
				{
					human = (Human)game.getCurrentPlayer();
				}
				setRollDiceEnabled();
			}
		}
		public void showOptions()
		{
			options.clear();
			options = game.getOptions(diceResult, optionsNumber);
			
			if (options.size() != 0)
			{
				choosePinDialog = new AlertDialog.Builder(GameActivity.this);
				choosePinDialog.setTitle(R.string.game_choose);
				simpleAdapter = new SimpleAdapter(GameActivity.this, options, R.layout.options_format, new String[]{"1","2","3","4","5"}, new int[]{R.id.actualState,R.id.firstArrow,R.id.nextState,R.id.secondArrow,R.id.posNextState});
				choosePinDialog.setSingleChoiceItems(simpleAdapter, -1, new onChosenPin());
				choosePinDialog.setPositiveButton(R.string.game_view_board, new onViewBoard());
			}
			else
			{
				choosePinDialog = new AlertDialog.Builder(GameActivity.this);
				choosePinDialog.setTitle(R.string.game_choose);
				choosePinDialog.setMessage(R.string.game_no_options);
				choosePinDialog.setPositiveButton("Ok", new onViewBoard());
			}
			runOnUiThread(new Runnable() 
			{						
				@Override
				public void run() 
				{
					choosePinDialog.show();
				}
			});
		}
		public void showWinner()
		{
			if (winner != null)
			{
				finish = new AlertDialog.Builder(GameActivity.this);
				finish.setTitle(R.string.game_over);
				finish.setMessage(winner.getName() + R.string.game_won);
				finish.setIcon(R.drawable.trophy);
				finish.setNeutralButton("Ok", new onFinishGame());
				runOnUiThread(new Runnable() 
				{				
					@Override
					public void run() 
					{
						finish.show();
					}
				});
			}
		}
		
		@Override
		public void run() 
		{
			while(!game.finished())
			{
				if (game.getCurrentPlayer().equals(human) && go)
				{
					go = false;
					try
					{
						this.sleep(Constants.pinStep);
					}
					catch(Exception e){}
					
					showOptions();
					while(!humanSelectedPin){}
					humanSelectedPin = false;
					if (pin != null)
					{
						humanMustPlay(pin);
					}
					try
					{
						this.sleep(Constants.turnChange);
					}
					catch(Exception e){}
				}
			}
			showWinner();
		}
		public class onChosenPin implements DialogInterface.OnClickListener
		{
			@Override
			public void onClick(DialogInterface dialog, int which)
			{
				viewOptions.setEnabled(false);
				GameActivity.HumanThread.this.pin = human.getPins().get(optionsNumber.get(which));
				humanSelectedPin = true;
				dialog.dismiss();
			}
		}
		public class onViewBoard implements DialogInterface.OnClickListener
		{
			@Override
			public void onClick(DialogInterface dialog, int which) 
			{
				if (options.size() != 0)
				{					
					return;
				}
				pin = null;
				humanSelectedPin = true;				
				viewOptions.setEnabled(false);
				finishTurn();
			}
		}
		public class onFinishGame implements DialogInterface.OnClickListener
		{				
			@Override
			public void onClick(DialogInterface dialog, int which) 
			{
				GameActivity.this.finish();
			}
		}
	}
}
